﻿//+-------------------------------------------------------------------------------------------------------------------------------------------------------
//
//Description:  ResourceExtensionReferenceViewModel
//Author: Aravind Ramarathinam (aravr@microsoft.com)
//
//Copyright © Microsoft Corporation
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
//INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
// 
//--------------------------------------------------------------------------------------------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
using VmroleAuthor.Commands;
using VmroleAuthor.Models;

namespace VmroleAuthor.ViewModels
{
    /// <summary>
    /// In v1 only one res ext reference is supported
    /// </summary>
    public class ResourceExtensionReferenceViewModel : TreeViewItemViewModel, IDataErrorInfo, IProduceValidationResults
    {
        private List<ResourceExtensionReference> resourceExtensionReferences;
        // In v1 only one res ext reference is supported
        private ResourceExtensionReference resourceExtensionReference;
        private readonly ObservableCollection<TreeViewItemViewModel> rootTreeViewItemViewModels;
        private readonly VmRoleResourceDefinitionViewModel vmRoleResourceDefinitionViewModel;
        private readonly ObservableCollection<ResourceExtensionParameterHolderViewModel> resourceExtensionParameterHolderViewModels;
        readonly VMRoleResourceExtensionDefinition markerDummyResExtDef;
        readonly VmRoleResourceExtensionViewModel markerDummyResExt;

        private VmRoleResourceExtensionViewModel selectedAutoPopulateResourceExtensionViewModel;

        public static class PropertyNames
        {
            public const string Summary = "Summary";
            public const string ReferenceName = "ReferenceName";
            public const string Name = "Name";
            public const string Publisher = "Publisher";
            public const string Version = "Version";
            public const string SelectedAutoPopulateResourceExtensionReference = "SelectedAutoPopulateResourceExtensionReference";
            public const string ResourceExtensionParameterHolderViewModels = "ResourceExtensionParameterHolderViewModels";
        }

        public ResourceExtensionReferenceViewModel(
            List<ResourceExtensionReference> resExtReferences,
            VmRoleResourceDefinitionViewModel resourceDefinitionViewModel,
            ObservableCollection<TreeViewItemViewModel> rootTreeViewItemViewModels)
            : base(resourceDefinitionViewModel, true)
        {
            this.resourceExtensionReferences = resExtReferences;
            this.rootTreeViewItemViewModels = rootTreeViewItemViewModels;
            if ((this.resourceExtensionReferences != null)&&(this.resourceExtensionReferences.Count > 0))
            {
                // In v1 only one res ext reference is supported
                this.resourceExtensionReference = this.resourceExtensionReferences[0];
            }
            this.vmRoleResourceDefinitionViewModel = resourceDefinitionViewModel;

            this.resourceExtensionParameterHolderViewModels = new ObservableCollection<ResourceExtensionParameterHolderViewModel>();
            if (this.resourceExtensionReference != null)
            {
                foreach (var holder in this.resourceExtensionReference.ResourceExtensionParameterHolders)
                {
                    var resourceExtensionParameterHolderViewModel = new ResourceExtensionParameterHolderViewModel(holder, null, this.vmRoleResourceDefinitionViewModel, false);
                    this.resourceExtensionParameterHolderViewModels.Add(resourceExtensionParameterHolderViewModel);
                }
            }          
            this.AddResourceExtensionParameterHolderCommand = new RelayCommand(
                param => this.AddResourceExtensionParameterHolder(),
                param => this.CanAddResourceExtensionParameterHolder());

            this.DeleteResourceExtensionParameterHolderCommand = new RelayCommand(
                param => this.DeleteResourceExtensionParameterHolder((ResourceExtensionParameterHolderViewModel)param),
                param => this.CanDeleteResourceExtensionParameterHolder((ResourceExtensionParameterHolderViewModel)param));

            this.ValidationResults = new ObservableCollection<CloudServiceModelValidationResult>();  
       
            this.markerDummyResExtDef = new VMRoleResourceExtensionDefinition { Name = "Unbind" };
            this.markerDummyResExt = new VmRoleResourceExtensionViewModel(markerDummyResExtDef, null);
        }

        public string Summary
        {
            get
            {
               if (this.resourceExtensionReferences == null || this.resourceExtensionReferences.Count == 0)
               {
                   return "Extension References (Not Set)";
               }
               return "Extension References (" + this.resourceExtensionReferences.Count + ")";
            }
        }    

        public string ReferenceName
        {
            get
            {
                return this.resourceExtensionReference != null ? this.resourceExtensionReference.ReferenceName : string.Empty;
            }
            set
            {
                this.AddOrUpdateResourceExtensionReferenceIfNotExisting();         
                this.resourceExtensionReference.ReferenceName = value;
                this.OnPropertyChanged(PropertyNames.ReferenceName);
                this.OnPropertyChanged(PropertyNames.Summary);
            }
        }

        public string Name
        {
            get
            {
                return this.resourceExtensionReference != null ? this.resourceExtensionReference.Name : string.Empty;
            }
            set
            {               
                this.AddOrUpdateResourceExtensionReferenceIfNotExisting();
                this.resourceExtensionReference.Name = value;
                this.OnPropertyChanged(PropertyNames.Name);
                this.OnPropertyChanged(PropertyNames.Summary);
            }
        }

        public string Publisher
        {
            get
            {
                return this.resourceExtensionReference != null ? this.resourceExtensionReference.Publisher : string.Empty;
            }
            set
            {
                this.AddOrUpdateResourceExtensionReferenceIfNotExisting();
                this.resourceExtensionReference.Publisher = value;
                this.OnPropertyChanged(PropertyNames.Publisher);
                this.OnPropertyChanged(PropertyNames.Summary);
            }
        }

        public string Version
        {
            get
            {
                return this.resourceExtensionReference != null ? this.resourceExtensionReference.Version : string.Empty;
            }
            set
            {
                this.AddOrUpdateResourceExtensionReferenceIfNotExisting();
                this.resourceExtensionReference.Version = value;
                this.OnPropertyChanged(PropertyNames.Version);
                this.OnPropertyChanged(PropertyNames.Summary);          
            }
        }

        public ObservableCollection<VmRoleResourceExtensionViewModel> AvailableResourceExtensionReferencesForAutoPopulate
        {
            get
            {
                var availbleResExtViewModels = new ObservableCollection<VmRoleResourceExtensionViewModel>();
                foreach (VmRoleResourceExtensionViewModel vmr in rootTreeViewItemViewModels.OfType<VmRoleResourceExtensionViewModel>())
                {
                    availbleResExtViewModels.Add(vmr);                                
                }


                availbleResExtViewModels.Add(this.markerDummyResExt);
                return availbleResExtViewModels;
            }
        }

        public VmRoleResourceExtensionViewModel SelectedAutoPopulateResourceExtensionReference
        {
            get { return this.selectedAutoPopulateResourceExtensionViewModel; }
            set
            {
                VmRoleResourceExtensionViewModel oldResourceExtensionViewModel = this.GetReferenceResourceExtension();
                this.selectedAutoPopulateResourceExtensionViewModel = value; 
                if (this.selectedAutoPopulateResourceExtensionViewModel == markerDummyResExt)
                {
                    this.selectedAutoPopulateResourceExtensionViewModel = null;
                }
                if (
                    (value == this.markerDummyResExt) ||
                    (oldResourceExtensionViewModel != this.selectedAutoPopulateResourceExtensionViewModel)||
                    (this.selectedAutoPopulateResourceExtensionViewModel != null && this.resourceExtensionParameterHolderViewModels.Count != this.selectedAutoPopulateResourceExtensionViewModel.ResourceExtensionParametersViewModel.Parameters.Count)
                    )
                {
                    if (oldResourceExtensionViewModel != null)
                    {
                        oldResourceExtensionViewModel.ResourceExtensionParametersViewModel.Parameters.CollectionChanged -= this.ReferencedResourceExtensionParametersCollectionChanged;
                        oldResourceExtensionViewModel.ResourceExtensionRequirementsViewModel.OSVirtualHardDiskRequirements.CollectionChanged -= this.ReferencedResourceExtensionTagsCollectionChanged;
                    }
                   
                    List<string> parametersToRemove = new List<string>();
                    foreach (ResourceExtensionParameterHolderViewModel resourceExtensionParameterHolderViewModel in this.resourceExtensionParameterHolderViewModels)
                    {
                        List<string> paramNames;
                        string newErr = ParameterizationHelper.ValidateAndGetParameterNames(resourceExtensionParameterHolderViewModel.ResourceExtensionParameterHolder.ParameterValue, out paramNames);
                        if (String.IsNullOrEmpty(newErr) && paramNames.Count > 0)
                        {
                            parametersToRemove.AddRange(paramNames);
                        }
                    }

                    if (this.selectedAutoPopulateResourceExtensionViewModel != null)
                    {
                        this.Name = this.selectedAutoPopulateResourceExtensionViewModel.Name;
                        this.Publisher = this.selectedAutoPopulateResourceExtensionViewModel.Publisher;
                        this.Version = this.selectedAutoPopulateResourceExtensionViewModel.Version;
                        this.ReferenceName = this.selectedAutoPopulateResourceExtensionViewModel.Name; //v1 only 1 ref
                    }
                    else
                    {
                        this.resourceExtensionReferences = null;
                        this.resourceExtensionReference = null;
                        this.vmRoleResourceDefinitionViewModel.ResourceDefinition.ResourceExtensionReferences = null;
                        this.vmRoleResourceDefinitionViewModel.ViewDefViewModel.ClearOsvhdRequirementTags();
                    }

                    this.vmRoleResourceDefinitionViewModel.ResourceDefinitionParametersViewModel.RemoveParameters(parametersToRemove);
                    this.resourceExtensionParameterHolderViewModels.Clear();  
                    //this.resourceExtensionReference.ResourceExtensionParameterHolders.Clear();
                    if (this.selectedAutoPopulateResourceExtensionViewModel != null)
                    {
                        foreach (ParameterViewModel availParam in this.selectedAutoPopulateResourceExtensionViewModel.ResourceExtensionParametersViewModel.Parameters)
                        {
                            ResourceExtensionParameterHolder holder = new ResourceExtensionParameterHolder
                                {
                                    ParameterName = availParam.Name
                                };
                            ResourceExtensionParameterHolderViewModel holderViewModel = new ResourceExtensionParameterHolderViewModel(holder, availParam, this.vmRoleResourceDefinitionViewModel, true);
                            this.resourceExtensionParameterHolderViewModels.Add(holderViewModel);
                            Debug.Assert(this.resourceExtensionReference != null &&  this.resourceExtensionReference.ResourceExtensionParameterHolders != null);                            
                            this.resourceExtensionReference.ResourceExtensionParameterHolders.Add(holderViewModel.ResourceExtensionParameterHolder);
                        }

                        if (this.selectedAutoPopulateResourceExtensionViewModel.ResourceExtensionDefinition.ResourceRequirements != null)
                        {
                            this.vmRoleResourceDefinitionViewModel.ViewDefViewModel.UpdateOsvhdRequirementTags(null, this.selectedAutoPopulateResourceExtensionViewModel.ResourceExtensionDefinition.ResourceRequirements.OSVirtualHardDiskRequirements);   
                        }                        

                        this.selectedAutoPopulateResourceExtensionViewModel.ResourceExtensionParametersViewModel.Parameters.CollectionChanged += this.ReferencedResourceExtensionParametersCollectionChanged;
                        this.selectedAutoPopulateResourceExtensionViewModel.ResourceExtensionRequirementsViewModel.OSVirtualHardDiskRequirements.CollectionChanged += this.ReferencedResourceExtensionTagsCollectionChanged;
                    }
                    else
                    {
                        this.vmRoleResourceDefinitionViewModel.ViewDefViewModel.RemoveApplicationSettingsViewDefSectionOnResExtUnbind();
                    }
                }
                this.OnPropertyChanged(PropertyNames.SelectedAutoPopulateResourceExtensionReference);
                this.OnPropertyChanged(PropertyNames.Name);
                this.OnPropertyChanged(PropertyNames.Publisher);
                this.OnPropertyChanged(PropertyNames.Version);
                this.OnPropertyChanged(PropertyNames.ReferenceName);
                this.OnPropertyChanged(PropertyNames.Summary);
            }
        }

        /// <summary>
        /// This is the event when referenced resext's properties change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void ReferencedResourceExtensionParametersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
            {
                if ((this.resourceExtensionReference != null) &&
                    (this.resourceExtensionReference.ResourceExtensionParameterHolders != null))
                {
                    List<string> paramsToRemove = new List<string>();
                    foreach (ParameterViewModel oldResExtParamViewModel in e.OldItems)
                    {                     
                        var resourceExtensionParameterHolderViewModel =
                            this.resourceExtensionParameterHolderViewModels.FirstOrDefault(
                                resParamHolder =>
                                string.Equals(resParamHolder.ParameterName, oldResExtParamViewModel.Name, StringComparison.Ordinal));

                        if (resourceExtensionParameterHolderViewModel != null)
                        {
                            paramsToRemove.Add(resourceExtensionParameterHolderViewModel.GetConstructedParamNameInResdef());
                            this.resourceExtensionParameterHolderViewModels.Remove(resourceExtensionParameterHolderViewModel);
                            Debug.Assert(this.resourceExtensionReference.ResourceExtensionParameterHolders != null);
                            this.resourceExtensionReference.ResourceExtensionParameterHolders.Remove(resourceExtensionParameterHolderViewModel.ResourceExtensionParameterHolder);
                        }                                        
                    }

                    this.resourceExtensionReference.SetResourceExtensionParameterValues();                 
                    this.vmRoleResourceDefinitionViewModel.ResourceDefinitionParametersViewModel.RemoveParameters(paramsToRemove);
                }               
            }
            if (e.NewItems != null)
            {
                foreach (ParameterViewModel newResExtParamViewModel in e.NewItems)
                {
                    ResourceExtensionParameterHolder holder = new ResourceExtensionParameterHolder
                    {
                        ParameterName = newResExtParamViewModel.Name
                    };
                    ResourceExtensionParameterHolderViewModel holderViewModel = new ResourceExtensionParameterHolderViewModel(holder, newResExtParamViewModel, this.vmRoleResourceDefinitionViewModel, true);
                    this.resourceExtensionParameterHolderViewModels.Add(holderViewModel);                  
                    Debug.Assert(this.resourceExtensionReference != null);
                    Debug.Assert(this.resourceExtensionReference.ResourceExtensionParameterHolders != null);           
                    this.resourceExtensionReference.ResourceExtensionParameterHolders.Add(holderViewModel.ResourceExtensionParameterHolder);

                }
            }                  
        }


        /// <summary>
        /// This is the event when referenced resext's tags change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void ReferencedResourceExtensionTagsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            List<string> tagsToRemove = new List<string>();
            List<string> tagsToAdd = new List<string>();
            if (e.OldItems != null)
            {
                tagsToRemove.AddRange(from OsVhdTagViewModel tagViewModel in e.OldItems select tagViewModel.Tag);
            }
            if (e.NewItems != null)
            {
                tagsToAdd.AddRange(from OsVhdTagViewModel tagViewModel in e.NewItems select tagViewModel.Tag);
            }
            this.vmRoleResourceDefinitionViewModel.ViewDefViewModel.UpdateOsvhdRequirementTags(tagsToRemove, tagsToAdd);                     
        }


        public ObservableCollection<ResourceExtensionParameterHolderViewModel> ResourceExtensionParameterHolderViewModels
        {
            get
            {
                return this.resourceExtensionParameterHolderViewModels;
            }
        }

        public ICommand AddResourceExtensionParameterHolderCommand
        {
            get;
            private set;
        }

        public ICommand DeleteResourceExtensionParameterHolderCommand
        {
            get;
            private set;
        }

        public bool CanAddResourceExtensionParameterHolder()
        {
            return this.resourceExtensionReference != null;
        }

        public void AddResourceExtensionParameterHolder()
        {
            ResourceExtensionParameterHolder holder = new ResourceExtensionParameterHolder();
            var resourceExtensionParameterHolderViewModel = new ResourceExtensionParameterHolderViewModel(holder, null, this.vmRoleResourceDefinitionViewModel, false);
            this.resourceExtensionParameterHolderViewModels.Add(resourceExtensionParameterHolderViewModel);
            Debug.Assert(this.resourceExtensionReference != null);           
            this.resourceExtensionReference.ResourceExtensionParameterHolders.Add(resourceExtensionParameterHolderViewModel.ResourceExtensionParameterHolder);
        }

        public bool CanDeleteResourceExtensionParameterHolder(ResourceExtensionParameterHolderViewModel resourceExtensionParameterHolderViewModel)
        {
            return true;
        }

        public void DeleteResourceExtensionParameterHolder(ResourceExtensionParameterHolderViewModel resourceExtensionParameterHolderViewModel)
        {
            if (resourceExtensionParameterHolderViewModel != null)
            {
                if ((this.resourceExtensionReference != null) && (this.resourceExtensionReference.ResourceExtensionParameterHolders != null))
                {
                    this.resourceExtensionReference.ResourceExtensionParameterHolders.Remove(resourceExtensionParameterHolderViewModel.ResourceExtensionParameterHolder);
                }              
                this.resourceExtensionParameterHolderViewModels.Remove(resourceExtensionParameterHolderViewModel);
            }
        }

        private void AddOrUpdateResourceExtensionReferenceIfNotExisting()
        {
            // In v1 only one res ext reference is supported
            if (this.resourceExtensionReferences == null)
            {
                this.resourceExtensionReferences = new List<ResourceExtensionReference>();                
                this.vmRoleResourceDefinitionViewModel.ResourceDefinition.ResourceExtensionReferences = this.resourceExtensionReferences;
            }
            if (this.resourceExtensionReferences.Count >= 1)
            {
                this.resourceExtensionReference = this.resourceExtensionReferences[0];
            }
            else
            {
                this.resourceExtensionReference = new ResourceExtensionReference();
                this.resourceExtensionReferences.Add(this.resourceExtensionReference); 
            }                      
        }

        private VmRoleResourceExtensionViewModel GetReferenceResourceExtension()
        {
            VmRoleResourceExtensionViewModel resourceExtensionViewModel = null;
            if (!string.IsNullOrEmpty(this.Name) && !string.IsNullOrEmpty(this.Publisher) && !string.IsNullOrEmpty(this.Version))
            {
                foreach (TreeViewItemViewModel rootTreeViewItemViewModel in rootTreeViewItemViewModels)
                {
                    VmRoleResourceExtensionViewModel vmr = rootTreeViewItemViewModel as VmRoleResourceExtensionViewModel;
                    if (vmr != null)
                    {
                        if (string.CompareOrdinal(vmr.Name, this.Name) == 0 && string.CompareOrdinal(vmr.Publisher, this.Publisher) == 0 && string.CompareOrdinal(vmr.Version, this.Version) == 0)
                        {
                            resourceExtensionViewModel = vmr;
                            break;
                        }
                    }
                }
            }
            return resourceExtensionViewModel;
        }

        protected override void LoadChildren()
        {
           // No children                          
        }

        public string this[string propertyName]
        {
            get
            {
                if (this.resourceExtensionReference == null)
                {
                    return null;
                }
                string error = null;                
                switch (propertyName)
                {
                    case PropertyNames.ReferenceName:                    
                        error = ParameterizationHelper.ValidateCannotBeParameterizedBySpecification(this.ReferenceName);
                        break;
                    case PropertyNames.Name:
                        error = ParameterizationHelper.ValidateCannotBeParameterizedBySpecification(this.Name);
                        break;
                    case PropertyNames.Version:
                        error = ValidationHelper.ValidateVersionFormat(this.Version);
                        break;
                    case PropertyNames.Publisher:
                        error = ParameterizationHelper.ValidateCannotBeParameterizedBySpecification(this.Publisher);                             
                        break;
                    case PropertyNames.SelectedAutoPopulateResourceExtensionReference:
                        //Validate all params are referenced
                        VmRoleResourceExtensionViewModel vmr = this.GetReferenceResourceExtension();
                        if (vmr != null)
                        {
                            if (vmr.ResourceExtensionParametersViewModel.Parameters.Count != this.resourceExtensionParameterHolderViewModels.Count)
                            {
                                error = string.Format(
                                        "Parmas in referenced extension Name: {0}, Publisher: {1}, Version {2}, not matching the declared resource ext params. Count of Params in ResExt: {3}. Params declared in Resdef {4}. Select to auto-fix this.",
                                        vmr.Name ?? string.Empty,
                                        vmr.Publisher ?? string.Empty,
                                        vmr.Version ?? string.Empty,
                                        vmr.ResourceExtensionParametersViewModel.Parameters.Count,
                                        this.resourceExtensionParameterHolderViewModels.Count);
                                //TODO: check actual names
                            }

                            if (error == null)
                            {
                                // Check Tags are the same between viewdef os vhd parameter and resource requirements in res ext
                                if (this.selectedAutoPopulateResourceExtensionViewModel != null)
                                {
                                    List<string> osVhdReq =
                                        this.SelectedAutoPopulateResourceExtensionReference.ResourceExtensionDefinition.ResourceRequirements != null
                                            ? this.SelectedAutoPopulateResourceExtensionReference.ResourceExtensionDefinition.ResourceRequirements.OSVirtualHardDiskRequirements
                                            : new List<string>();
                                    List<string> viewDefImageTags = this.vmRoleResourceDefinitionViewModel.ViewDefViewModel.GetOsVhdRequirementsImageTags() ?? new List<string>();

                                    List<string> missingInViewDef = osVhdReq.Except(viewDefImageTags, StringComparer.OrdinalIgnoreCase).ToList();
                                    if (missingInViewDef.Count > 0)
                                    {
                                        error = string.Format(
                                            "OS VHD Requirement Tags specified referenced extension are not specified in ViewDefinition. Tags in ResExt: {0}. Tags in ViewDef {1}. Add image tags in ViewDef.",                                            
                                            string.Join(",", osVhdReq.ToArray()),
                                            string.Join(",", viewDefImageTags.ToArray())
                                       );
                                    }

                                    if (error == null)
                                    {
                                        List<string> missingInResExt = viewDefImageTags.Except(osVhdReq, StringComparer.OrdinalIgnoreCase).ToList();
                                        if (missingInResExt.Count > 0)
                                        {
                                            error = string.Format(
                                                "OS VHD Requirement Tags are specified in ViewDefinition but not in Resource Extension. Tags in ResExt: {0}. Tags in ViewDef {1}. Remove tags in ViewDef or add in Resource Extension.",                                               
                                                string.Join(",", osVhdReq.ToArray()),
                                                string.Join(",", viewDefImageTags.ToArray())
                                                );
                                        }
                                    }

                                    // Block linux operating system profile resdef to bind to windows resext and vice-versa
                                    if (error == null)
                                    {
                                        error = ValidationHelper.ValidateOSProfileLinkage(this.selectedAutoPopulateResourceExtensionViewModel.ResourceExtensionDefinition, this.vmRoleResourceDefinitionViewModel.ResourceDefinition);
                                    }
                                }                                                             
                            }
                           
                        }

                        break;
                }                           
                return error;
            }           
        }

        public string Error { get; set; }

        public ObservableCollection<CloudServiceModelValidationResult> ValidationResults { get; private set; }

        public void UpdateValidationResults()
        {
            this.IsExpanded = true;
            ModelValidationResultsHelper.UpdateValidationResults(this, typeof(PropertyNames));
            //Add any other cross-field validations error            
        }    
    }
}
